
"""
Simple template engine.
Main intent to create small but powerful enought TE.
"""
# FIXME: document module.
# FIXME: if/else must be implemented

import settings as St
import re

class TemplateException(Exception) :
    def __init__(self, msg = 'Template error') :
        self.error = msg
    def __str__(self) :
        return self.error

class Node :
    """
    Abstract node class. Only purpose to serve as common base.
    """
    pass


class TextNode(Node) :
    "Node which insert itself as text."
    def __init__(self, string) :
        self.data = string
    def render(self, dct = None) :
        return self.data
    def dump(self, lvl) :
        print '  '*lvl + 'Text node'

        
class SubNode(Node) :
    "Substitute value"
    def __init__(self, key) :
        self.keys = key.split('.')
    def render(self, dct) : 
        obj = dct
        try:
            for key in self.keys :
                try: 
                    obj = getattr(obj, key)
                except AttributeError :
                    obj = obj[key]
            return unicode( obj )
        except KeyError :
            return u""
        
    def dump(self, lvl) :
        print '  '*lvl + 'Sub node:', self.keys


class RootNode(Node) :
    """
    Basic node with children. 
    """
    def __init__(self) :
        self.data = [] 
    def append(self, node) : 
        self.data.append(node) 
        return node
    def end(self,t) :
        if t != None :
            raise TemplateException('Unexpected end of file')
    def render(self, dct = None) :
        return ''.join( [ node.render(dct) for node in self.data] )
    def dumpchildren(self,lvl) :
        for node in self.data :
            node.dump(lvl+1)        
    def dump(self, lvl = 0):
        print '  '*lvl + 'Root node'
        self.dumpchildren(lvl)


class ForNode(RootNode) :
    "Repeating value"
    def __init__(self, itername, key) :
        self.key = key
        self.itername = itername
        RootNode.__init__(self)
    def end(self,t) :
        if t != 'for' :
            raise TemplateException('End of for expression expected')
    def render(self, dct) :
        def runitem(item) :
            dct[self.itername] = item
            return RootNode.render(self, dct)
        return ''.join( [ runitem(i) for i in dct.get(self.key, []) ] )
            
    def dump(self, lvl) :
        print '  '*lvl + 'For node', self.itername, '->', self.key
        self.dumpchildren(lvl)


class BlockNode(RootNode) :
    "Block node."
    def __init__(self, name) :
        RootNode.__init__(self)
    def end(self,t) :
        if t != 'block' :
            raise TemplateException('End of block expression expected')
    def dump(self,lvl) :
        print '  '*lvl + 'Block node'
        self.dumpchildren(lvl)
    
class Template :
    """
    Template class itself.
    """
    ## Long and complicated regular expressions for template splitting
    extend = re.compile("{{extend:(?P<filename>[\w.]*)}}")
    rex = re.compile(
        ("{{(?:" 
         "(?::(?P<sub>\w+(?:\.\w+)*))|"                                     ## Substitute
         "(?P<for>for:(?P<foriter>[a-zA-Z]\w*)->(?P<forkey>[a-zA-Z]\w*))|"  ## For
         "(?P<endfor>for)|"                                                 ## End for
         "(?:block:(?P<block>[a-zA-Z]\w*))|" 
         "(?P<endblock>block)" 
         ")}}") % {
        'ident' : '[a-zA-Z]\w*',
        } )
    
    def __init__(self, fname) :
        blocks = {}            # Dictionary for common blocks
        text = self.getTemplate(fname)

        def parse(node) :
            """
            Parses template text and split it to nodes.
            `node' is current parent node.
            """
            while 1 :
                m = self.rex.search(text, self.pos)
                if not m : 
                    node.append( TextNode(text[self.pos:]) )
                    node.end(None)
                    return node 
                node.append( TextNode( text[self.pos:m.start()] ) )
                self.pos = m.end()
                ## Actions for each tag
                if m.group('sub') :
                    node.append( SubNode( m.group('sub') ))
                elif m.group('for') :
                    fornode = parse( ForNode(m.group('foriter'), m.group('forkey')) )
                    node.append( fornode )
                elif m.group('endfor') :
                    node.end('for')
                    return node
                elif m.group('block') :
                    blocknode = parse( BlockNode(m.group('block')) )
                    node.append( blocks.get(m.group('block'), blocknode) )
                elif m.group('endblock') :
                    node.end('block')
                    return node
                else :
                    raise TemplateException('Unknown expression') 

        def parseExtend() :
            """
            Parse template which extends another template.
            """
            while 1 :
                m = self.rex.search(text, self.pos)
                if not m :
                    return
                self.pos = m.end()
                if m.group('block') :
                    node = parse( BlockNode(m.group('block')) )
                    if not blocks.has_key( m.group('block') ) :
                        blocks[ m.group('block') ] = node
                else :
                    raise TemplateException('Only block tags are allowed.') 

        def readTemplate() :
            """
            Parses template text and return root node of template.
            """
            m = self.extend.match(text)
            if m : 
                self.pos = m.end()
                parseExtend()
                return readTemplate(m.group('filename'))
            else :
                self.pos = 0
                return parse(RootNode() )
                      
        self.root = readTemplate()
        del self.pos

                
    def getTemplate(self, fname) :
        """
        Return file data by it's name. 
        fname - is name relative to Settings.TDir (Template dir)
        """
        f = open(St.TDir+'/'+fname)
        txt = f.read()
        f.close()
        return txt
            
    def render(self, dct) :
        return self.root.render(dct)

    def dump(self) :
        print '<pre>'
        self.root.dump()
        print '</pre>'
